﻿using AForge.Video.DirectShow;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media.Imaging;

namespace Black.CameraCapture
{
    /// <summary>
    /// Contrôle d'affichage d'un flux WebCam
    /// </summary>
    public partial class CameraCaptureControl : UserControl
    {
        #region Propriétés de dépendances

        public static readonly DependencyProperty AutoStartProperty = DependencyProperty.Register("AutoStart", typeof(bool), typeof(CameraCaptureControl), new PropertyMetadata(true, CameraCaptureControl.OnAutoSizeChanged));
        public static readonly DependencyProperty CurrentDeviceProperty = DependencyProperty.Register("CurrentDevice", typeof(CaptureDevice), typeof(CameraCaptureControl), new PropertyMetadata(null, CameraCaptureControl.OnCurrentDeviceChanged, CameraCaptureControl.OnCoerceCurrentDevice));
        public static readonly DependencyProperty CurrentDisplayProperty = DependencyProperty.Register("CurrentDisplay", typeof(DisplayDefinition), typeof(CameraCaptureControl), new PropertyMetadata(null, CameraCaptureControl.OnCurrentDisplayChanged, CameraCaptureControl.OnCoerceCurrentDisplay));
        public static readonly DependencyProperty DevicesProperty = DependencyProperty.Register("Devices", typeof(ReadOnlyCollection<CaptureDevice>), typeof(CameraCaptureControl), new PropertyMetadata(new ReadOnlyCollection<CaptureDevice>(CameraCaptureControl.GetCaptureDevices())));
        public static readonly DependencyProperty DisplaysProperty = DependencyProperty.Register("Displays", typeof(ReadOnlyCollection<DisplayDefinition>), typeof(CameraCaptureControl), new PropertyMetadata(null));
        public static readonly DependencyProperty IsStartedProperty = DependencyProperty.Register("IsStarted", typeof(bool), typeof(CameraCaptureControl), new PropertyMetadata(false));
        public static readonly DependencyProperty ResolutionModeProperty = DependencyProperty.Register("ResolutionMode", typeof(ResolutionMode), typeof(CameraCaptureControl), new PropertyMetadata(ResolutionMode.HighResolution));
        public static readonly DependencyProperty SizeModeProperty = DependencyProperty.Register("SizeMode", typeof(SizeMode), typeof(CameraCaptureControl), new PropertyMetadata(SizeMode.Auto));

        #endregion

        #region Propriétés

        /// <summary>
        /// Obtient ou définit si on démarre la capture automatiquement
        /// </summary>
        public bool AutoStart
        {
            get
            {
                return ((bool)this.GetValue(CameraCaptureControl.AutoStartProperty));
            }
            set
            {
                this.SetValue(CameraCaptureControl.AutoStartProperty, value);
            }
        }

        /// <summary>
        /// Obtient ou définit le périphérique en cours de capture
        /// </summary>
        public CaptureDevice CurrentDevice
        {
            get
            {
                return ((CaptureDevice)this.GetValue(CameraCaptureControl.CurrentDeviceProperty));
            }
            set
            {
                this.SetValue(CameraCaptureControl.CurrentDeviceProperty, value);
            }
        }

        /// <summary>
        /// Obtient ou définit la définition actuelle d'affichage
        /// </summary>
        public DisplayDefinition CurrentDisplay
        {
            get
            {
                return ((DisplayDefinition)this.GetValue(CameraCaptureControl.CurrentDisplayProperty));
            }
            set
            {
                this.SetValue(CameraCaptureControl.CurrentDisplayProperty, value);
            }
        }

        /// <summary>
        /// Obtient la liste des périphériques présent
        /// </summary>
        public ReadOnlyCollection<CaptureDevice> Devices
        {
            get
            {
                return ((ReadOnlyCollection<CaptureDevice>)this.GetValue(CameraCaptureControl.DevicesProperty));
            }
            private set
            {
                this.SetValue(CameraCaptureControl.DevicesProperty, value);
            }
        }

        /// <summary>
        /// Obtient la liste des affichages possibles
        /// </summary>
        public ReadOnlyCollection<DisplayDefinition> Displays
        {
            get
            {
                return ((ReadOnlyCollection<DisplayDefinition>)this.GetValue(CameraCaptureControl.DisplaysProperty));
            }
            private set
            {
                this.SetValue(CameraCaptureControl.DisplaysProperty, value);
            }
        }

        /// <summary>
        /// Obtient ou définit si la capture est démarée
        /// </summary>
        public bool IsStarted
        {
            get
            {
                return ((bool)this.GetValue(CameraCaptureControl.IsStartedProperty));
            }
            private set
            {
                this.SetValue(CameraCaptureControl.IsStartedProperty, value);
            }
        }

        /// <summary>
        /// Obtient ou définit le mode de résulution
        /// </summary>
        public ResolutionMode ResolutionMode
        {
            get
            {
                return ((ResolutionMode)this.GetValue(CameraCaptureControl.ResolutionModeProperty));
            }
            set
            {
                this.SetValue(CameraCaptureControl.ResolutionModeProperty, value);
            }
        }

        /// <summary>
        /// Obtient ou définit le mode de redimensionnement
        /// </summary>
        public SizeMode SizeMode
        {
            get
            {
                return ((SizeMode)this.GetValue(CameraCaptureControl.SizeModeProperty));
            }
            set
            {
                this.SetValue(CameraCaptureControl.SizeModeProperty, value);
            }
        }

        #endregion

        #region Variables d'instances

        private VideoCaptureDevice _videoCapture;

        #endregion

        #region Constructeurs

        /// <summary>
        /// Constructeur de la classe
        /// </summary>
        public CameraCaptureControl()
        {
            // Initialisationd du XAML

            this.InitializeComponent();

            // Initialisation

            this._videoCapture = null;
        }

        #endregion

        #region Fonctions publiques

        /// <summary>
        /// Prend une capture d'écran et la sauvegarde
        /// </summary>
        /// <param name="filename">Chemin du fichier</param>
        public void ScreenshotSave(string filename)
        {
            try
            {
                // Vérification

                if (!this.IsStarted)
                    return;

                // On prend l'image du flux

                Bitmap snapshot = this.CameraPlayer.GetCurrentVideoFrame();

                // On le sauvegarde

                snapshot.Save(filename);
            }
            catch (Exception e)
            {
                Debug.WriteLine(string.Format("Black Camera Capture => Unable to save screenshot : ", e.Message));
            }
        }

        /// <summary>
        /// Prend une capture d'écran
        /// </summary>
        /// <returns>Capture d'écran</returns>
        public BitmapImage ScreenshotTake()
        {
            try
            {
                // Vérification

                if (!this.IsStarted)
                    return (null);

                // On prend l'image du flux

                Bitmap snapshot = this.CameraPlayer.GetCurrentVideoFrame();

                // On la transforme en BitmapImage

                BitmapImage bitmapImage = null;

                using (MemoryStream memory = new MemoryStream())
                {
                    snapshot.Save(memory, ImageFormat.Png);
                    memory.Position = 0;

                    bitmapImage = new BitmapImage();
                    bitmapImage.BeginInit();
                    bitmapImage.StreamSource = memory;
                    bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
                    bitmapImage.EndInit();
                }

                // On retourne l'instance

                return (bitmapImage);
            }
            catch(Exception e)
            {
                Debug.WriteLine(string.Format("Black Camera Capture => Unable to take screenshot : ", e.Message));
                return (null);
            }
        }

        /// <summary>
        /// Démarre la capture
        /// </summary>
        public void Start()
        {
            // Vérification

            if (this.IsStarted)
                return;

            // On démarre

            this.CameraPlayer.Start();
            this.IsStarted = true;
        }

        /// <summary>
        /// Arrête la capture
        /// </summary>
        public void Stop()
        {
            // Vérification

            if (!this.IsStarted)
                return;

            // Arrêt du service

            this.CameraPlayer.SignalToStop();
            this.CameraPlayer.WaitForStop();
            this.CameraPlayer.Stop();

            this.IsStarted = false;
        }

        #endregion

        #region Fonctions protégées

        /// <summary>
        /// Se produit sur le changement de la propriété AutoSize
        /// </summary>
        /// <param name="oldValue">Ancienne valeur</param>
        /// <param name="newValue">Nouvelle valeur</param>
        protected virtual void OnAutoSizeChanged(bool oldValue, bool newValue)
        {
            this.AutoSizeContent();
        }

        /// <summary>
        /// Se produit sur la validation de la propriété CurrentDevice
        /// </summary>
        /// <param name="value">Valeur à traiter</param>
        /// <returns>Valeur traitée</returns>
        protected virtual object OnCoerceCurrentDevice(CaptureDevice value)
        {
            // Vérification NULL

            if (value == null)
                return (value);

            // Vérification si dans la liste des devices

            foreach (CaptureDevice item in this.Devices)
            {
                if (value == item)
                    return (value);
            }

            // Il n'est pas dans la liste, on invalide

            return (this.CurrentDevice);
        }

        /// <summary>
        /// Se produit sur la validation de la propriété CurrentDisplay
        /// </summary>
        /// <param name="value">Valeur à traiter</param>
        /// <returns>Valeur traitée</returns>
        protected virtual object OnCoerceCurrentDisplay(DisplayDefinition value)
        {
            // Vérification

            if (value == null)
                return (value);

            // Vérification si dans la liste des devices

            if (this.Displays != null)
            {
                foreach (DisplayDefinition item in this.Displays)
                {
                    if (value == item)
                        return (value);
                }
            }

            // Il n'est pas dans la liste, on invalide

            return (this.CurrentDisplay);
        }

        /// <summary>
        /// Se produit sur le changement de la propriété CurrentDevice
        /// </summary>
        /// <param name="oldValue">Ancienne valeur</param>
        /// <param name="newValue">Nouvelle valeur</param>
        protected virtual void OnCurrentDeviceChanged(CaptureDevice oldValue, CaptureDevice newValue)
        {
            // On arrête le traitement

            if (this.IsStarted)
                this.Stop();

            // Suppression des anciennes données

            this.CleanInstance();

            if (newValue == null)
                return;

            // On initie la nouvelle valeur

            this.InitializeCurrentDevice();

            // Si départ automatique on le traite

            if (this.AutoStart)
                this.Start();
        }

        /// <summary>
        /// Se produit sur le changement de la propriété CurrentDisplay
        /// </summary>
        /// <param name="oldValue">Ancienne valeur</param>
        /// <param name="newValue">Nouvelle valeur</param>
        protected virtual void OnCurrentDisplayChanged(DisplayDefinition oldValue, DisplayDefinition newValue)
        {
            this.AutoSizeContent();
        }

        #endregion

        #region Fonctions privées

        /// <summary>
        /// Mise en place de la taille du contrôle
        /// </summary>
        private void AutoSizeContent()
        {
            // Auto size actif ?

            if (this.SizeMode == CameraCapture.SizeMode.Manual || this.CurrentDisplay == null)
            {
                this.Container.Width = double.NaN;
                this.Container.Height = double.NaN;
            }
            else
            {
                this.Container.Width = this.CurrentDisplay.FrameWidth;
                this.Container.Height = this.CurrentDisplay.FrameHeight;
            }
        }

        /// <summary>
        /// Libère les ressources
        /// </summary>
        private void CleanInstance()
        {
            this.CameraPlayer.VideoSource = null;
            this.CurrentDisplay = null;
            this.Displays = null;
            this._videoCapture = null;
            this.IsStarted = false;
        }

        /// <summary>
        /// Met à jour les informations du contrôle suite à la modification du Device
        /// </summary>
        private void InitializeCurrentDevice()
        {
            // Si pa de device on ne fait rien

            if (this.CurrentDevice == null || string.IsNullOrWhiteSpace(this.CurrentDevice.ID))
                return;

            // Création de la capture

            this._videoCapture = new VideoCaptureDevice(this.CurrentDevice.ID);
            this.CameraPlayer.VideoSource = this._videoCapture;

            // Création des displays

            List<DisplayDefinition> list = new List<DisplayDefinition>();

            foreach (VideoCapabilities display in this._videoCapture.VideoCapabilities)
                list.Add(new DisplayDefinition(display));

            // On tri la liste

            list.Sort();

            // On affecte la liste

            this.Displays = new ReadOnlyCollection<DisplayDefinition>(list);

            // Choix du display

            if (this.ResolutionMode == CameraCapture.ResolutionMode.HighResolution)
                this.CurrentDisplay = this.Displays[this.Displays.Count - 1];
            else
                this.CurrentDisplay = this.Displays[0];
        }

        #endregion

        #region Fonctions statiques privées

        /// <summary>
        /// Récupère la liste des périphériques présent sur le poste
        /// </summary>
        /// <returns>Liste des périphériques</returns>
        private static List<CaptureDevice> GetCaptureDevices()
        {
            // Création de la liste

            List<CaptureDevice> list = new List<CaptureDevice>();

            // Récupération des définitions des périphériques

            FilterInfoCollection devices = new FilterInfoCollection(FilterCategory.VideoInputDevice);

            foreach (FilterInfo info in devices)
                list.Add(new CaptureDevice(info));

            // On la tri

            list.Sort();

            // On retourne la liste

            return (list);
        }

        /// <summary>
        /// Se produit sur le changement de la propriété AutoSize
        /// </summary>
        ///  <param name="sender">Source de l'appel</param>
        /// <param name="e">Argument de l'appel</param>
        private static void OnAutoSizeChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            ((CameraCaptureControl)sender).OnAutoSizeChanged((bool)e.OldValue, (bool)e.NewValue);
        }

        /// <summary>
        /// Se produit sur la validation de la propriété CurrentDevice
        /// </summary>
        /// <param name="sender">Source de l'appel</param>
        /// <param name="baseValue">Valeur à traiter</param>
        /// <returns>Valeur traitée</returns>
        private static object OnCoerceCurrentDevice(DependencyObject sender, object baseValue)
        {
            return (((CameraCaptureControl)sender).OnCoerceCurrentDevice(baseValue as CaptureDevice));
        }

        /// <summary>
        /// Se produit sur la validation de la propriété Currentisplay
        /// </summary>
        /// <param name="sender">Source de l'appel</param>
        /// <param name="baseValue">Valeur à traiter</param>
        /// <returns>Valeur traitée</returns>
        private static object OnCoerceCurrentDisplay(DependencyObject sender, object baseValue)
        {
            return (((CameraCaptureControl)sender).OnCoerceCurrentDisplay(baseValue as DisplayDefinition));
        }

        /// <summary>
        /// Se produit sur le changement de la propriété CurrentDevice
        /// </summary>
        /// <param name="sender">Source de l'appel</param>
        /// <param name="e">Argument de l'appel</param>
        private static void OnCurrentDeviceChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            ((CameraCaptureControl)sender).OnCurrentDeviceChanged((CaptureDevice)e.OldValue, (CaptureDevice)e.NewValue);
        }

        /// <summary>
        /// Se produit sur le changement de la propriété Currentisplay
        /// </summary>
        /// <param name="sender">Source de l'appel</param>
        /// <param name="e">Argument de l'appel</param>
        private static void OnCurrentDisplayChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            ((CameraCaptureControl)sender).OnCurrentDisplayChanged((DisplayDefinition)e.OldValue, (DisplayDefinition)e.NewValue);
        }

        #endregion
    }
}
